#include "Cache.h"
#include <sstream>
#include <iostream>
#include "ComponentsHolder.h"
using namespace std;
string CacheState;
Cache::Cache(long size, double readCacheSize, double writeCacheSize, CacheManagement policy): _availableTime(0), _writeCount(0), _ioCount(0), _totalIOCount(0)
{
	_totalWriteCount = 0;
	_size = (size*(GB/(SimulationParameters::CacheAlignment*KB)));
	_readSize = (readCacheSize/100.0)*_size;
	_writeSize = (writeCacheSize/100.0)*_size;
	_dirtyBitcounter = 0;

	_policy = policy;
	CacheState = SimulationParameters::OutputDirectory + "CacheState.txt";
	_readList = new List();
	_writeList = new List();
	ofstream f(CacheState);
	f.close();
	f.open(SimulationParameters::OutputDirectory + "CacheUtilization.csv");
	f << "Time,Total Extents in cache,Read,Write,Percenatge\n";
	f.close();

	_extents = new map<string, ListNode*>();
}


Cache::~Cache(void)
{
	_readList->clear();
	delete _readList;
	_writeList->clear();
	delete _writeList;
	delete _extents;
}

double Cache::getNextTime(void)
{
	double simTime =  ComponentsHolder::getInstance()->getEventsHandler()->getTime();
	_availableTime = MAX(_availableTime,simTime);

	double time = _availableTime;
	_availableTime += CacheSearchDelay;
	return time;
}
bool Cache::existsInCache(ExtentIO* eio)
{
	string s = getIOID(eio);
	if (_extents->find(s) == _extents->end())
		return false;
	return true;
}
bool Cache::exists(ExtentIO* eio)
{
	if (existsInCache(eio))
	{
		ListNode* node = _extents->at(getIOID(eio));
		ExtentIO* oldData = node->getData();

		if (eio->getType() == Write && oldData->getType() == Read)
		{
			ExtentIO* removeData = _readList->remove(node);
			if (removeData != oldData)
			{
				std::cout << "Problem with remove!!!\n";
				throw "Problem with remove!!!\n";
			}
			
			ComponentsHolder::getInstance()->getCache()->decreaseDirtyCounter();	
			node = _writeList->add(eio);
			delete oldData;
		}
		else if (eio->getType() == Read && oldData->getType() == Write)
		{	
			eio->setType(Write); // stay in the write list
			node->setData(eio);
			_writeList->moveToEnd(node);
		}
		else if (eio->getType() == Write && oldData->getType() == Write)
		{	
			node->setData(eio);
			_writeList->moveToEnd(node);
		}
		else if (eio->getType() == Read && oldData->getType() == Read)
		{	
			node->setData(eio);
			_readList->moveToEnd(node);
		}
		_extents->at(getIOID(eio)) = node;
		
		return true;
	}
	return false;
}

void Cache::insert(ExtentIO* eio)
{
	if (_extents->find(getIOID(eio)) != _extents->end())
	{
		exists(eio);
	//	cout << "impossible situatation - insert cache";
	//	throw "impossible situatation";
		return;
	}
	ListNode* nodeAdded;
	int space = hasSpace(eio);
	static bool first = true;
	if (first && space < 0)
	{
		first = false;
		ofstream f;
		f.open(SimulationParameters::OutputDirectory + "CacheUtilization.csv", ios::app);
		f << "Time of first time that the cache is full: " << ComponentsHolder::getInstance()->getEventsHandler()->getTime() << endl;
		f.close();
	}
	List *l = eio->getType() == Read ? _readList : _writeList;
	nodeAdded = l->add(eio);
	_extents->insert(pair<string, ListNode*>(getIOID(eio), nodeAdded));	
	int chunks = eio->getSize(getCacheAlignment());
	for (int i = 1; i < chunks; i++)
	{
		ExtentIO* e = new ExtentIO(*eio);
		e->setOffset(eio->getOffset() + (i*getCacheAlignment()));
		if (_extents->find(getIOID(e)) != _extents->end())
			delete e;
		else
		{
			nodeAdded = l->add(e);
			_extents->insert(pair<string, ListNode*>(getIOID(e), nodeAdded));	
		}
	}
	if (space < 0)
		removeLatestExtents(space, eio->getType() == Write);
}
void Cache::removeLatestExtents(int count, bool isWrite)
{
	while (count < 0)
	{
		ExtentIO* head = _readList->getSize() == 0 ? _writeList->removeHead() : _readList->removeHead();
		if (head->isFromWrite() || isWrite )
			ComponentsHolder::getInstance()->getCache()->decreaseDirtyCounter();
		_extents->erase(_extents->find(getIOID(head)));
		if (SimulationParameters::flashPolicy == Regular)
			ComponentsHolder::getInstance()->getFlashCache()->insert(head);
		else
			delete head;
		count++;
	}

}
void Cache::deleteFromWriteCache(string extentID)
{
	ExtentIO* eio  = removeFromWrite(extentID);
	addToRead(eio);
}

void Cache::increaseDirtyCounter(){
	_dirtyBitcounter++;
}

void Cache::decreaseDirtyCounter(){
	if (_dirtyBitcounter>0)
		_dirtyBitcounter--;
}

int Cache::getDirtyCounter(){
	return _dirtyBitcounter;
}

int Cache::getWriteCount() const
{
	return _writeCount;

}
void Cache::increaseWrites()
{
	_writeCount++;
	_totalWriteCount++;
}
void Cache::resetWriteCount()
{
	_writeCount = 0;
}
int Cache::getTotalWriteCount() const
{
	return _totalWriteCount;
}

int Cache::getTotalIOCount() const
{
	return _totalIOCount;
}
int Cache::getIOCount() const
{
	return _ioCount;
}
void Cache::increaseIOs()
{
	_ioCount++;
	_totalIOCount++;
}
void Cache::resetIOCount()
{
	_ioCount = 0;
}

double Cache::getWritePercentage()
{
	return ((double)_writeList->getSize()) / (_writeSize + _readSize);
}
ExtentIO* Cache::getNextWrite()
{
	return (_writeList->getSize() > 0) ? _writeList->getHead()->getData() : 0;
}

double Cache::getCachePercentage()
{
	ofstream f(SimulationParameters::OutputDirectory + "CacheUtilization.csv", ios::app);
	if (  _readList->getSize() + _writeList->getSize() != _extents->size())
	{
		cout << "Inconsistent cache\n";
		throw "Inconsistent cache\n";
	}
	f << (int) ComponentsHolder::getInstance()->getEventsHandler()->getTime() << "," << _readList->getSize() + _writeList->getSize() << "," << _readList->getSize() << "," << _writeList->getSize() << ",";
	
	double cacheSize = _readSize + _writeSize;
	double currentSize = _extents->size();

	double percentage = (currentSize / cacheSize)*100;
	//f << percentage << endl;

	double writePercent = ((double)_writeList->getSize() / cacheSize)*100;

	f << writePercent <<","<<currentSize<< endl;
	f.close();
	return percentage;
}
int Cache::hasSpace(ExtentIO* eio)
{
	if (eio->getType() == Read){
		int chunks = eio->getSize(getCacheAlignment());
		return ((_readSize + MIN(0,(_writeSize - _writeList->getSize()))) -  (_readList->getSize() + chunks - ComponentsHolder::getInstance()->getCache()->getDirtyCounter()));	
	}
	else if (eio->getType() == Write)
		return (SimulationParameters::WriteAllowance*(_writeSize + _readSize)) - (_readList->getSize() + _writeList->getSize() + eio->getSize(getCacheAlignment()));
	cout << "Invalid type\n";
	throw "Invalid type";
}

ExtentIO* Cache::removeFromWrite(string extentID)
{
	_extents->erase(_extents->find(extentID));
	
	ExtentIO* oldHead = _writeList->removeHead();
	if (getIOID(oldHead) != extentID)
	{
		cout << "Problem with remove head from write!!!\n";
		throw "Problem with remove head from write!!!\n";
	}
	return oldHead;
}

void Cache::addToRead(ExtentIO* eio)
{
	ListNode* nodeAdded;
	eio->setType(Read);
	ComponentsHolder::getInstance()->getCache()->increaseDirtyCounter();
	eio->setFromWrite();
	nodeAdded = _readList->add(eio);
	_extents->insert(pair<string, ListNode*>(getIOID(eio), nodeAdded));
}

string Cache::getIOID(ExtentIO* eio)
{
	return eio->getIOID(getCacheAlignment());
}